# 转换完整项目 

import re
import os
import subprocess
import argparse
import shutil

# 正则表达式模式
if_pattern = r"#if\s+.*"
elif_pattern = r"#elif\s+.*"
end_pattern = r"#endif"
function_pattern = r"\w+\s+\w+\s*\(.*\)\s*{"  # 示例函数模式，匹配函数定义
function_end_pattern = r"}"  # 用于检测函数体结束的模式

# 宏栈和函数栈
macro_stack = []
function_stack = []

def get_macro_expression(line):
    line = line.strip()
    if line.startswith("#if "):
        return line[4:].strip()
    elif line.startswith("#elif "):
        return line[6:].strip()
    else:
        raise ValueError(f"Unexpected line format for macro extraction: {line}")

def extract_macros_from_header(header_file):
    macros = {}
    with open(header_file, 'r') as file:
        for line in file:
            if line.startswith('#define'):
                parts = line.split()
                if len(parts) >= 2:
                    macros[parts[1]] = parts[2].strip('\n') if len(parts) > 2 else "1"
    return macros

def generate_makefile(c_files_dir):
    c_files = []
    for root, _, files in os.walk(c_files_dir):
        for file in files:
            if file.endswith('.c'):
                c_files.append(os.path.relpath(os.path.join(root, file), c_files_dir))

    if not c_files:
        print(f"No C files found in {c_files_dir}. Please ensure there are .c files.")
        return False

    c_files_string = ' '.join(c_files)
    makefile_content = f"""
CC = gcc
CFLAGS = -Wall -Iinclude
SRCS = {c_files_string}
OBJS = $(SRCS:.c=.o)

all: $(OBJS)

%.o: %.c
\t$(CC) $(CFLAGS) -c $< -o $@

clean:
\trm -f $(OBJS)
"""

    makefile_path = os.path.join(c_files_dir, "Makefile")
    if os.path.exists(makefile_path):
        print(f"Makefile already exists at: {makefile_path}. Skipping generation.")
        return True

    try:
        with open(makefile_path, "w") as makefile:
            makefile.write(makefile_content)
        print(f"Makefile generated at: {makefile_path}")
        return True
    except Exception as e:
        print(f"An error occurred while generating Makefile: {e}")
        return False

def find_included_headers(c_file_path, c_files_dir):
    included_headers = []
    with open(c_file_path, 'r') as c_file:
        for line in c_file:
            if line.startswith('#include'):
                header_file = re.search(r'#include\s+"(.+)"', line)
                if header_file:
                    header_name = header_file.group(1)
                    # 尝试在当前目录和父目录中查找
                    header_path = os.path.join(os.path.dirname(c_file_path), header_name)
                    if os.path.exists(header_path):
                        included_headers.append(header_path)
                    else:
                        # 如果在当前目录没有找到，检查其他预定义的目录（例如：c_files_dir）
                        header_path = os.path.join(c_files_dir, header_name)
                        if os.path.exists(header_path):
                            included_headers.append(header_path)
                        else:
                            print(f"Warning: Could not find header {header_name} for file {c_file_path}")
    return included_headers

def handle_macros_in_file(c_file, header_macros, c_file_path, c_files_dir):
    collecting_function = False
    current_function_code = ""
    current_macro_conditions = []
    included_headers = find_included_headers(c_file_path, c_files_dir)

    rust_file_content = ""

    for line in c_file:
        if re.match(if_pattern, line):
            macro_stack.append(get_macro_expression(line))
            current_macro_conditions.append(get_macro_expression(line))
        elif re.match(elif_pattern, line):
            if not macro_stack:
                print("Warning: elif encountered without a corresponding if.")
                continue
            macro_stack[-1] = get_macro_expression(line)
            current_macro_conditions[-1] = get_macro_expression(line)
        elif re.match(end_pattern, line):
            if macro_stack:
                macro_stack.pop()
                current_macro_conditions.pop()
            else:
                print("Warning: endif encountered without a corresponding if.")
        elif re.match(function_pattern, line):
            collecting_function = True
            current_function_code = line
        elif collecting_function:
            current_function_code += line
            if re.search(function_end_pattern, line):
                collecting_function = False
                rust_code = convert_function_to_rust(current_function_code, header_macros, included_headers)
                if not current_macro_conditions:
                    rust_file_content += rust_code + "\n\n"
                else:
                    macro_expression = " && ".join(current_macro_conditions)
                    rust_code_with_macro = f"#[cfg({macro_expression})]\n{rust_code}"
                    rust_file_content += rust_code_with_macro + "\n\n"

    # 如果没有提取到函数内容，添加原 C 文件内容的注释
    if not rust_file_content.strip():
        with open(c_file_path, 'r') as file:
            rust_file_content = "// C code:\n" + file.read()
    
    return rust_file_content

def convert_function_to_rust(c_code, header_macros, included_headers):
    rust_code = c_code
    for macro, value in header_macros.items():
        rust_code = rust_code.replace(macro, str(value))
    
    # 添加头文件作为注释
    for header in included_headers:
        rust_code = f"// Imported header: {header}\n" + rust_code

    # 如果没有转换函数内容，则提供一个占位符代码
    if not rust_code.strip():
        rust_code = "// Placeholder for Rust conversion\n"

    return rust_code

def create_rust_project(rust_output_dir):
    if os.path.exists(rust_output_dir):
        shutil.rmtree(rust_output_dir)
    subprocess.run(["cargo", "new", rust_output_dir])
    print(f"Rust project created at: {rust_output_dir}")

def process_c_files_and_generate_rust(c_files_dir, rust_output_dir):
    create_rust_project(rust_output_dir)

    if not os.path.exists(os.path.join(c_files_dir, "Makefile")):
        print("Generating Makefile...")
        if not generate_makefile(c_files_dir):
            print("Failed to generate Makefile.")
            return

    print("Generating compile_commands.json...")
    try:
        subprocess.run(["bear", "--", "make", "-C", c_files_dir], check=True)
    except subprocess.CalledProcessError as e:
        print(f"Warning during compile_commands.json generation: {e}. Some files may be skipped.")
        # Move forward even if some files fail to compile to try to generate `compile_commands.json`

    print("Converting C to Rust...")
    try:
        result = subprocess.run(["c2rust", "transpile", "-e", "-o", rust_output_dir, "compile_commands.json"],
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        if result.returncode != 0:
            print(f"Warning during C2Rust conversion: {result.stderr}")
        else:
            print("C2Rust conversion completed successfully.")
    except Exception as e:
        print(f"An error occurred during Rust conversion: {e}")

    header_files = []
    for root, _, files in os.walk(c_files_dir):
        for file in files:
            if file.endswith('.h'):
                header_files.append(os.path.join(root, file))

    header_macros = {}
    for header_file in header_files:
        macros = extract_macros_from_header(header_file)
        header_macros.update(macros)

    for root, _, files in os.walk(c_files_dir):
        for c_file_name in files:
            if c_file_name.endswith('.c'):
                try:
                    c_file_path = os.path.join(root, c_file_name)
                    with open(c_file_path, 'r') as c_file:
                        rust_file_content = handle_macros_in_file(c_file, header_macros, c_file_path, c_files_dir)

                    # 保持原始目录结构
                    rel_path = os.path.relpath(root, c_files_dir)
                    rust_dir_path = os.path.join(rust_output_dir, "src", rel_path)
                    os.makedirs(rust_dir_path, exist_ok=True)

                    rust_file_name = c_file_name[:-2] + ".rs"
                    rust_file_path = os.path.join(rust_dir_path, rust_file_name)
                    with open(rust_file_path, "w") as rust_file:
                        rust_file.write(rust_file_content)
                    print(f"Rust code written to {rust_file_path}")

                except Exception as e:
                    print(f"Warning: Failed to process {c_file_name} due to: {e}")

    # Copy non-C files and directories to the Rust project
    for root, dirs, files in os.walk(c_files_dir):
        for dir_name in dirs:
            src_dir = os.path.join(root, dir_name)
            if not os.path.exists(src_dir):
                continue
            rel_dir = os.path.relpath(src_dir, c_files_dir)
            dest_dir = os.path.join(rust_output_dir, "src", rel_dir)
            if not os.path.exists(dest_dir):
                shutil.copytree(src_dir, dest_dir)
                print(f"Copied directory {src_dir} to {dest_dir}")

    print("All files processed.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Convert C project to Rust")
    parser.add_argument("c_files_dir", help="Directory of C source files")
    parser.add_argument("rust_output_dir", help="Directory to generate the Rust project")
    args = parser.parse_args()

    process_c_files_and_generate_rust(args.c_files_dir, args.rust_output_dir)
